/*
Brick And Blast, a crossplatform breakout game.
 Copyright (C) 2008-2010 Julien CLEMENT (andematunum@gmail.com)
 
 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

#include "Ball.h"
#include "Bat.h"
#include "BatPart.h"
#include "Bumper.h"
#include "Edge.h"
#include "Brick.h"
#include "Collider.h"

/**
* file   Ball.cpp
* date   Ven 25 déc 2009 20:47:00 CET
* brief  TODO
*/

Ball::
Ball(SDL_Point& initPosition,
     SDL_TextureID initTexture,
     double velocity)
  : GameObject(GAME_OBJECT_BALL,GAME_OBJECT_CIRCLE,initPosition,initTexture),
    velocity_(velocity), center_(initPosition.x,initPosition.y),
    direction_(0.,0.), accel_decel_(1.) {
    set_position(initPosition);
}

void
Ball::
move () {
  center_ += direction_*(velocity_*accel_decel_);
  if (accel_decel_ > 1.001) {
    accel_decel_/=1.05;
  }
  else {
    accel_decel_ = 1;
  }
}

void
Ball::
update_direction () {
  if (forces_.empty()) {
    return;
  }
  Vector2d sum_forces;
  for (set<Vector2d*>::iterator itr = forces_.begin();
       itr != forces_.end(); itr++) {
       sum_forces += **itr;
  }
  Vector2d tmp;
  tmp = direction_ + sum_forces;
  // For some reason, the resulting vector can be null.
  // In that case it means that sum_forces = -direction_
  // so we directly set direction_ = -direction_
  // It's not mathematically coherent, but it gives
  // the expected result.
  if (tmp.is_null()) {
   cout << "Correction" << endl;
   direction_ = -direction_;
  }
  tmp.get_normalized(direction_);
  forces_.clear();
}

void
Ball::
calculate_next_pixel_position () {
  Vector2d next_center = center_ + direction_*(velocity_*accel_decel_);
  next_center.round_int().get_SDL_Point(pixel_position_next_);
  pixel_position_changed_ = 
   ((pixel_position_next_.x != pixel_position_.x) || (pixel_position_next_.y != pixel_position_.y));
  if (pixel_position_changed_) {
    calculate_next_pixel_rect();
  }
}

bool
Ball::
handle_game_area_collisions () {
  int radius = size_.r;
  bool collides = false;
  if (pixel_position_next_.x - radius < 16) {
    collide_screen_left();
  }
  if (pixel_position_next_.x + radius >= 1008) {
    collide_screen_right();
  }
  if (pixel_position_next_.y - radius < 16) {
    collide_screen_up();
  }
  if (pixel_position_next_.y + radius >= 672) {
   set_active(false);
   set_velocity(0.);
  }
}
 
void
Ball::
handle_collision (GameObject* obj, const ColliderData& data) {
  switch(obj->get_type()) {
   case GAME_OBJECT_BALL: {
      handle_collision_circle(data);
      // Communicate the current accel-decel factor
      if (accel_decel_ > 1.) {
        static_cast<Ball*>(obj)->set_accel_decel(accel_decel_/2.);
      }
   }
   break;
   case GAME_OBJECT_BAT_BORDER_LEFT:
   case GAME_OBJECT_BAT_BORDER_RIGHT:
   case GAME_OBJECT_BAT_CHUNK: {
    BatPart* part = static_cast<BatPart*>(obj);
    handle_collision(part, data);
   }
   break;
   case GAME_OBJECT_BUMPER: {
      handle_collision_circle(data);
      set_accel_decel(3.5);
   }
   break;
   case GAME_OBJECT_EDGE: {
    Edge* edge = static_cast<Edge*>(obj);
    // Avoid corner collisions with non corner
    // edges.
    // Corner configs are: 0,2,4,6
    bool handled = true;
    if (!(data.config & 0x1)) {
      Edge::EdgeAttribute attrib = edge->get_attrib();
      switch (attrib) {
        case Edge::EDGE_DEFAULT: {
          handled = false;
        }
        break;
        case Edge::EDGE_CORNER_LEFT: {
          if ((data.config == 0) || (data.config == 6)) {
            handled = false;
          }
        }
        break;
        case Edge::EDGE_CORNER_RIGHT: {
          if ((data.config == 2) || (data.config == 4)) {
            handled = false;
          }
        }
        break;
        default: {
        }
        break;
      }
    }
   
    if (handled) {
      handle_collision_rectangle(data);
    }
   }
   break;
   case GAME_OBJECT_BRICK: {
    Brick* brick = static_cast<Brick*>(obj);
    handle_collision_rectangle(data);
    handle_collision(brick, data);
   }
   break;
   default: {
     cout << "*** WARNING: Collision with object of type: " << obj->get_type() << " not handled." << endl;
   }
   break;
  }
}

void
Ball::
handle_collision (BatPart* bat, const ColliderData& data) {
  const SDL_Rect* bat_rect = bat->get_bat()->get_rect();
  // Translate to the real origin of our problem:
  // Do as if the left border of the bat minus the radius of
  // the ball is the origin, for which the angle is +PI
  int orig = bat_rect->x - size_.r;
  int width = bat_rect->w + 2*size_.r;
  double angle = (double)((center_.get_x() - orig - width) * M_PI) / (double)width;
  direction_.set (cos(angle), sin(angle));
}

void
Ball::
handle_collision (Edge* edge, const ColliderData& data) {
}

void
Ball::
handle_collision_circle (const ColliderData& data) {
  if (data.from_object == this) {
    forces_.insert(new Vector2d(data.impact_vector));
  }
  else {
    forces_.insert(new Vector2d(-data.impact_vector));
  }
}

void
Ball::
handle_collision_rectangle (const ColliderData& data) {
  switch(data.config) {
    /** Collision with a corner: it is a point 2 point collision
      * so use the impact vector. */
    case 0:
    case 2:
    case 4:
    case 6: {
      forces_.insert(new Vector2d(-data.impact_vector));
    }
    return;
    /** Collision with an edge: it is point 2 line collision
      * so the adjustement vector depends only on the
      * line verticality. */
    case 1:
    case 5: {
      forces_.insert(new Vector2d(
        0., -2.*direction_.get_y()
      ));
    }
    return;

    case 3:
    case 7: {
      forces_.insert(new Vector2d(
        -2.*direction_.get_x(), 0.
      ));
    }
    return;

    default: {
    }
    return;
  }
}

void
Ball::
handle_collision (Brick* brick, const ColliderData& data) {
}

